package org.rowan.adi.components.comp2d;

import java.util.LinkedList;
import org.rowan.adi.Vector;
import org.rowan.adi.components.comp2d.polygon.Polygon;
import org.rowan.adi.linearalgebra.LinearAlgebra2D;

/**
 * The Component2D class is an abstract class that generalizes points,
 * line segments, and shapes in two dimensional space. All 2D Components have a
 * trajectory and are comprised of a list of 2D Points in clockwise order.
 *
 * @author Robert Russell
 * @version 1.0
 */
public abstract class Component2D {

    private Vector trajectory; // The trajectory of the Component.
    private LinkedList<Point2D> points; // The points which make up this Component.
    private Vector location; // The location of the Component.
    private boolean initialize = true; // The initialization flag required for setting the location.

    /**
     * Determines whether the child Component is wholly contained by this Component.
     * @param child the child Component2D.
     * @return <code>true</code> if the child Component2D is wholly contained
     * by this Component; <code>false</code> otherwise.
     */
    public final boolean contains(Component2D component) {
        return LinearAlgebra2D.contains(this, component);
    }

    /**
     * Computes the closest distance between this Component and the Component instance given.
     * @param comp2 an instance of Componet2D.
     * @return the closest distance between this Component and the Component instance given.
     */
    public final double distance(Component2D component) {
        return LinearAlgebra2D.distance(this, component);
    }

    /**
     * Gets a list containing all of the 2D points which make up this 2D Component.
     * @return a list containing all of the 2D points which make up this 2D Component.
     */
    public final LinkedList<Point2D> getListofPoints() {
        return this.points;
    }

    /**
     * Gets the location of the 2D Component.
     * @return an array containing the X and Y locations of the 2D Component.
     */
    public final Vector getLocation() {
        return this.location;
    }

    /**
     * Gets the trajectory of this 2D Component.
     * @return the trajectory of this 2D Component.
     */
    public final Vector getTrajectory() {
        return this.trajectory;
    }

    /**
     * Computes the cross-sectional area of this Component2D with the Component2D instances given.
     * @param component an instance of Component2D.
     * @return an instance of IrregularConvexPolygon, which represents the
     * cross-sectional area of the two Component2D instances.
     * @throws IllegalArgumentException instances of Circle cannot be passed into
     * this version of intersection because the area returned is not an instance
     * of Component but the actual value of the area. non-polygon objects will
     * be implemented in future releases.
     */
    public final Component2D intersect(Component2D component) {
        return LinearAlgebra2D.intersect(this, component);
    }

    /**
     * Determines whether this Component will intersect the boundary Component2D
     * in a given amount of time. It is assumed that this Component2D is moving.
     * @param boundary an instance of Component2D which represents the potential
     * point of intersection. This boundary is stationary.
     * @param time the the maximum amount of time given for the Component2D
     * instances to intersect. The units for time is seconds.
     * @return <code>true</code> if this Component2D will intersect the
     * boundary Component2D in a given amount of time; <code>false</code> otherwise.
     * @throws IllegalArgumentException the boundary cannot be moving or the moving
     * Component is stationary.
     */
    public final boolean intersectBoundaryComponent(Component2D boundary, double time) {
        return LinearAlgebra2D.intersect(this, boundary, time);
    }

    /**
     * Determines whether the moving Component2D will intersect this Component2D. It
     * is assumed that this Component2D is stationary.
     * @param moving an instance of Component2D which has a non-zero trajectory.
     * @param time the the maximum amount of time given for the Component2D
     * instances to intersect. The units for time is seconds.
     * @return <code>true</code> if the moving Component2D will intersect this
     * Component2D in a given amount of time; <code>false</code> otherwise.
     * @throws IllegalArgumentException the boundary cannot be moving or the moving
     * Component is stationary.
     */
    public final boolean intersectMovingComponent(Component2D moving, double time) {
        return LinearAlgebra2D.intersect(moving, this, time);
    }

    /**
     * Sets the list containing all of the 2D points which make up this 2D Component
     * to a given list.
     * @param the list containing all of the 2D points which make up this 2D Component.
     */
    protected final void setListofPoints(LinkedList<Point2D> points) {
        this.points = points;
    }

    /**
     * Sets the location of the 2D Component. The location of a 2D Component is
     * the center point in the bounding box of the 2D Component.
     * @param location the new location of the 2D component.
     */
    public final void setLocation(Vector location) {
        // Component is being initialized for the first time
        if (initialize) {
            this.location = location;
            initialize = false;
            return;
        }

        // This component is a point itself, so need to check additional points
        if (getListofPoints().size() == 1) {
            this.location = location;
            return;
        }

        // Determine the change in X and Y between the current and new coordinates.
        double deltaX = Math.abs(this.location.getX() - location.getX());
        double deltaY = Math.abs(this.location.getY() - location.getY());

        boolean xSubtract;
        boolean ySubtract;

        // Determines whether the X and Y will decrease or increase. This is
        // important, as we need to know whether to add or subtract the deltas
        // to the current points.
        if (this.location.getX() > location.getX()) {
            xSubtract = true;
        } else {
            xSubtract = false;
        }

        if (this.location.getY() > location.getY()) {
            ySubtract = true;
        } else {
            ySubtract = false;
        }

        // Update the coordinates of each point to reflect the new location.
        for (Point2D point : this.getListofPoints()) {
            if (xSubtract) {
                point.setX(point.getX() - deltaX);
            } else {
                point.setX(point.getX() + deltaX);
            }

            if (ySubtract) {
                point.setY(point.getY() - deltaY);
            } else {
                point.setY(point.getY() + deltaY);
            }
        }

        this.location = location;
    }

    /**
     * Sets the the trajectory of this 2D Component to the given Vector.
     * @param trajectory the Vector that represents the trajectory of this 2D Component.
     */
    public final void setTrajectory(Vector trajectory) {
        this.trajectory = trajectory;
    }
}
